#include <timberyard/db.h>
#include <pqxx/connection.hxx>

namespace Timberyard {

namespace Internal {
	class DBTrigger : public pqxx::trigger, private Uncopyable {
		public:
			DBTrigger(DBPool& pool, const std::string& name, std::tr1::function< void() > fn) :
				pqxx::trigger(pool.GetNotifConnection(),name), m_fn(fn),m_pool(pool) {};
			virtual ~DBTrigger() throw () {};
			virtual void operator()(int i) {m_pool.NotifyHandler(m_fn);}
		protected:
			std::tr1::function< void() > m_fn;
			DBPool& m_pool;
	};
} // namespace Internal


DBTransaction::DBTransaction(DBPool& pool,const std::string& name)
	: pqxx::internal::namedclass("DBTransaction"),pqxx::transaction<>(pool.Request(),name), m_pool(pool)
{}

DBTransaction::DBTransaction(DBPool& pool)
	: pqxx::internal::namedclass("DBTransaction"),pqxx::transaction<>(pool.Request()), m_pool(pool) 
{}

DBTransaction::~DBTransaction() throw () {
	// We leak here. DBPools can't easily be destroyed since they're
	// threaded. The triggers aren't tracked, and individual
	// connections are likely still to be in use.
	
	// If you rely on this working, you'll hit a sigsegv eventually.
	m_pool.Release(conn()); 
}

pqxx::connection_base& DBPool::Request() {
	boost::mutex::scoped_lock lock(m_mutex);
	pqxx::connection_base* con;
	if (m_free.empty()) {
		con=new pqxx::connection(m_connection);
	} else {
		con=m_free.front().first;
		m_free.pop_front();
	}
	return *con;
}

void DBPool::Release(pqxx::connection_base& con) {
	boost::mutex::scoped_lock lock(m_mutex);
	m_free.push_front(std::make_pair(&con,time(0)));
}

void DBPool::Tidy(unsigned int maxage) {
	// free up any connections unused for maxage seconds
	boost::mutex::scoped_lock lock(m_mutex);
	time_t when=time(0)-maxage;
	while (!m_free.empty() && (m_free.back().second<when)) {
		delete m_free.back().first;
		m_free.pop_back();
	}
}

pqxx::connection_base&  DBPool::GetNotifConnection() {
	if (0==m_notif_con);
	m_notif_con=new pqxx::connection(m_connection);
	return *m_notif_con;
}

void DBPool::NotifyHandler(std::tr1::function<void ()> fn) {
	if (0==m_threadpool) {
		// do them ourselves
		fn();
	} else {
		m_threadpool->Exec(fn);
	}
}

void DBPool::NotifyHandlerThread() {
	while (1) {
		while (m_notif_queue.size()!=0) {
			std::pair<std::string,std::tr1::function<void()> > p=m_notif_queue.Pop();
			new Internal::DBTrigger(*this,p.first,p.second);
		}
		m_notif_con->await_notification(1,0);
	}
}

void DBPool::BackgroundNotifications(ThreadPool* pool) {
	static bool already=false;
	if (already) throw Exception("BackgroundNotifcations already running")<<THROW_LOCATION;
	already=true;
	boost::mutex::scoped_lock lock(m_mutex);
	if (0==m_notif_con) this->GetNotifConnection();
	if ((m_threadpool=pool)) {
		new boost::thread(std::tr1::bind(&DBPool::NotifyHandlerThread,this));
	} else {
		pool->Exec(std::tr1::bind(&DBPool::NotifyHandlerThread,this));
	}
}

void DBPool::NotificationCallback(const std::string& name,std::tr1::function< void ()> fn) {
	// This is asyncronous!
	m_notif_queue.Push(std::make_pair(name,fn));
}

}
